//
// Latte.cs
//
// Copyright (C) 2OO8 Lukas Lipka <lukaslipka@gmail.com>
//

using System;
using System.Collections.Generic;

using Dashboard;
using Dashboard.Util;
using Dashboard.Engine;
using Dashboard.Mint.Tiles;
using Dashboard.Mint.Widgets;

using Gtk;
using Gdk;

namespace Dashboard.Latte
{
	public class Latte : Gtk.Window
	{
		private Notebook notebook = null;
		private Sidebar sidebar = null;
		private TilePage tile_page = null;

		private MessageBroker message_broker = null;

		private Dictionary<Tile, TileFlat> tiles = new Dictionary<Tile, TileFlat> ();

		public Latte () : base  ("Latte")
		{
			base.AppPaintable = true;
			base.Decorated = false;
			base.SkipPagerHint = true;

			base.SkipTaskbarHint = true;
			base.BorderWidth = 14;
			//base.KeepAbove = true;
			//base.TypeHint = Gdk.WindowTypeHint.Splashscreen;

			DefaultWidth = 800;
			DefaultHeight = 600;

			HBox box = new HBox ();

			notebook = new Notebook ();
			notebook.ShowBorder = false;
			notebook.ShowTabs = false;
			box.PackStart (notebook, true, true, 0);

			sidebar = new Sidebar ();
			box.PackStart (sidebar, false, true, 0);

			VBox vbox = new VBox (false, 0);

			SlideBar slide_bar = new SlideBar ();
			vbox.PackStart (slide_bar, false, true, 0);

			tile_page = new TilePage ();
			vbox.PackStart (tile_page, true, true, 0);

			InformationBar info_bar = new InformationBar ();
			info_bar.Icon = Dashboard.Mint.Util.LoadStockIcon (Stock.DialogInfo, 32);
			info_bar.Text = "<b>Dashboard " + ConfigureDefines.Version + "</b>\n\n" +
				"Welcome to the first preview version of Dashboard. Keep in " +
				"mind that this version is still under heavy development and " +
				"may not work and behave as expected. However, please do report " +
				"any difficulties or bugs that you experience while using it.";
			info_bar.AddAction ("I love you guys", delegate { info_bar.Hide (); });

			AnimatedWidget animation = new AnimatedWidget (500);
			animation.Add (info_bar);
			vbox.PackStart (animation, false, true, 0);

			notebook.AppendPage (vbox, null);

			//ScrolledWindow port = new ScrolledWindow ();
			//port.AddWithViewport (tile_page);
			//notebook.AppendPage (port, null);

			// Enable translucency if available			
			if (Screen.RgbaColormap != null) {
				base.Colormap = Screen.RgbaColormap;
			}

			Add (box);
			Reposition ();

			// FIXME: Yeah, that naming isn't that great.
			message_broker = Dashboard.Engine.Engine.MessageBroker;
			message_broker.Subscribe ("/event/*", typeof(ClueMessage), OnClueMessage);
			message_broker.Subscribe ("/event/match/found", typeof(MatchMessage), OnMatchMessage);

			XKeybinder.Bind ("<Super>X", OnToggleVisibility);

			animation.Grow ();
		}

		private void OnToggleVisibility (object o, EventArgs args)
		{
			if (Visible) {
				Hide ();
			} else {
				ShowAll ();
				Present ();
			}
		}

		protected override bool OnExposeEvent (Gdk.EventExpose evnt)
		{
			if (!IsDrawable)
				return true;

			Cairo.Context gr = Gdk.CairoHelper.Create (evnt.Window);

			//gr.Rectangle (Allocation.X, Allocation.Y, Allocation.Width, Allocation.Height);
			gr.Color = new Cairo.Color (1.0, 1.0, 1.0, 0.0);
			gr.Operator = Cairo.Operator.Source;
			gr.Paint ();
			
			Gdk.Color border = Style.DarkColors [(int)StateType.Normal];
			Gdk.Color fill = Style.BaseColors [(int)StateType.Normal];

			// Border			
			CairoFu.RoundedRectangle (gr, Allocation.X + 0.5, Allocation.Y + 0.5, Allocation.Width - 0.5, Allocation.Height - 0.5, 10);
			gr.LineWidth = 1;
			gr.Color = CairoFu.GdkColorToCairoColor (border, 0.8);
			gr.Operator = Cairo.Operator.Source;
			gr.FillPreserve ();

			// Border frame
			gr.Color = CairoFu.GdkColorToCairoColor (border);
			gr.Operator = Cairo.Operator.Over;
			gr.Stroke ();

			// Container
			gr.Rectangle (Allocation.X + 14, Allocation.Y + 14, Allocation.Width - 28, Allocation.Height - 28);
			gr.Color = CairoFu.GdkColorToCairoColor (fill);
			gr.Operator = Cairo.Operator.Over;
			gr.Fill ();
			
			CairoFu.DisposeContext (gr);
		       
			return base.OnExposeEvent (evnt);
		}

		protected override void OnSizeRequested (ref Requisition requisition)
		{
			Rectangle position;
			GetPosition (out position.X, out position.Y);

			int n = Screen.GetMonitorAtPoint (position.X, position.Y);
			Rectangle screen = Screen.GetMonitorGeometry (n);

			requisition.Width = screen.Width - 100;
			requisition.Height = screen.Height - 100;

			if (Child != null && Child.Visible) {
				Requisition child = Child.SizeRequest ();
				requisition.Width = Math.Max (requisition.Width, child.Width);
				requisition.Height = Math.Max (requisition.Height, child.Height);
			}
		}

		private void Reposition ()
		{
			Gdk.Rectangle alloc;

			GetPosition (out alloc.X, out alloc.Y);
			GetSize (out alloc.Width, out alloc.Height);

			int screen = Screen.GetMonitorAtPoint (alloc.X, alloc.Y);
			Gdk.Rectangle geometry = Screen.GetMonitorGeometry (screen);

			alloc.X = (geometry.Width - alloc.Width) / 2;
			alloc.Y = (int)((geometry.Height - alloc.Height) / 2.5);

			Move (alloc.X, alloc.Y);
		}

		private void OnClueMessage (Message message)
		{
			// Save the clue message id and window id 
			// so that we can map matches to the right 
			// workspace later.
			
			ClueMessage clue_message = message as ClueMessage;

			//windows_by_clue_message_id [clue_message.Id] = clue_message.Window;

			// If the replaces property is set, we remove all 
			// tiles that are matched to the clue that we're
			// replacing.
			
			if (!String.IsNullOrEmpty (clue_message.Replaces)) {
				List<Tile> tiles_to_remove = GetTilesByClueMessageId (clue_message.Replaces);

				// FIXME: Don't assume it's the same window
				// as the clue message it's replacing.
				//TilePage tile_page = GetTilePage (Wnck.Window.Get (clue_message.Window).Workspace.Number.ToString ());
				
				foreach (Tile tile in tiles_to_remove) {
					tiles.Remove (tile);
					tile_page.RemoveTile (tiles [tile]);
				}
			}
		}

		private void OnMatchMessage (Message message)
		{
			MatchMessage match_message = message as MatchMessage;
	
			// This gets invoked in it's own thread, so
			// proxy it to the main thread.

			ThreadAssist.ProxyToMain (delegate {
				foreach (Match match in match_message.Matches) {
					//Wnck.Window window = Wnck.Window.Get (windows_by_clue_message_id [match.Trigger]);
					//TilePage tile_page = GetTilePage (window.Workspace.Number.ToString ());
					
					Tile tile = TileFactory.BuildTile (match);

					if (tile == null)
						continue;

					//tile.Window = window.Xid;

					TileFlat flat = new TileFlat (tile);
					flat.Selected += OnTileSelected;
					tile_page.AddTile (flat);
					tile_page.ShowAll ();

					flat.Pulse ();

					tiles.Add (tile, flat);

					//if (window.Workspace.Number == screen.ActiveWorkspace.Number)
					//	FocusTilePage (window.Workspace.Number.ToString ());
				}
			});
		}

		private void OnTileSelected (Tile tile)
		{
			sidebar.Update (tile);
		}

		private List<Tile> GetTilesByXid (ulong xid)
		{
			List<Tile> found = new List<Tile> ();

			foreach (Tile tile in tiles.Keys) {
				if (tile.Window == xid)
					found.Add (tile);
			}

			return found;
		}

		private List<Tile> GetTilesByClueMessageId (string id)
		{
			List<Tile> found = new List<Tile> ();

			foreach (Tile tile in tiles.Keys) {
				if (tile.Match.Trigger == id)
					found.Add (tile);
			}

			return found;			
		}
	}
}
